package cn.sylinx.hbatis.type;

import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.time.Year;
import java.time.YearMonth;
import java.time.ZonedDateTime;
import java.time.chrono.JapaneseDate;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public final class TypeHandlerRegistry {

	private final Map<Type, TypeHandler<?>> TYPE_HANDLER_MAP = new ConcurrentHashMap<Type, TypeHandler<?>>();

	private final UnknownTypeHandler UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler();

	private static TypeHandlerRegistry instance = null;

	public static TypeHandlerRegistry getInstance() {

		if (instance == null) {
			initInstance();
		}

		return instance;
	}

	private synchronized static void initInstance() {

		if (instance == null) {
			instance = new TypeHandlerRegistry();
		}
	}

	private TypeHandlerRegistry() {
		register(Boolean.class, new BooleanTypeHandler());
		register(boolean.class, new BooleanTypeHandler());

		register(Byte.class, new ByteTypeHandler());
		register(byte.class, new ByteTypeHandler());

		register(Short.class, new ShortTypeHandler());
		register(short.class, new ShortTypeHandler());

		register(Integer.class, new IntegerTypeHandler());
		register(int.class, new IntegerTypeHandler());

		register(Long.class, new LongTypeHandler());
		register(long.class, new LongTypeHandler());

		register(Float.class, new FloatTypeHandler());
		register(float.class, new FloatTypeHandler());

		register(Double.class, new DoubleTypeHandler());
		register(double.class, new DoubleTypeHandler());

		register(Reader.class, new ClobReaderTypeHandler());
		register(String.class, new StringTypeHandler());

		register(BigInteger.class, new BigIntegerTypeHandler());

		register(BigDecimal.class, new BigDecimalTypeHandler());

		register(InputStream.class, new BlobInputStreamTypeHandler());
		register(Byte[].class, new ByteObjectArrayTypeHandler());
		register(byte[].class, new ByteArrayTypeHandler());

		register(Object.class, new ObjectTypeHandler());

		register(Date.class, new DateTypeHandler());

		register(java.sql.Date.class, new SqlDateTypeHandler());
		register(java.sql.Time.class, new SqlTimeTypeHandler());
		register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());

		register(Instant.class, new InstantTypeHandler());
		register(LocalDateTime.class, new LocalDateTimeTypeHandler());
		register(LocalDate.class, new LocalDateTypeHandler());
		register(LocalTime.class, new LocalTimeTypeHandler());
		register(OffsetDateTime.class, new OffsetDateTimeTypeHandler());
		register(OffsetTime.class, new OffsetTimeTypeHandler());
		register(ZonedDateTime.class, new ZonedDateTimeTypeHandler());
		register(Month.class, new MonthTypeHandler());
		register(Year.class, new YearTypeHandler());
		register(YearMonth.class, new YearMonthTypeHandler());
		register(JapaneseDate.class, new JapaneseDateTypeHandler());

		register(Character.class, new CharacterTypeHandler());
		register(char.class, new CharacterTypeHandler());
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public <T> TypeHandler<T> getTypeHandler(Class<T> type) {

		TypeHandler<?> handler = TYPE_HANDLER_MAP.get((Type) type);

		if (handler == null && type != null && type instanceof Class && Enum.class.isAssignableFrom((Class<?>) type)) {
			handler = new EnumTypeHandler((Class<?>) type);
		}
		return (TypeHandler<T>) handler;
	}

	@SuppressWarnings("unchecked")
	public <T> void register(TypeHandler<T> typeHandler) {
		TypeReference<T> typeReference = (TypeReference<T>) typeHandler;
		register(typeReference.getRawType(), typeHandler);
	}

	public <T> void register(Class<T> javaType, TypeHandler<? extends T> typeHandler) {
		register((Type) javaType, typeHandler);
	}

	private <T> void register(Type javaType, TypeHandler<? extends T> typeHandler) {
		TYPE_HANDLER_MAP.put(javaType, typeHandler);
	}

	public <T> void register(TypeReference<T> javaTypeReference, TypeHandler<? extends T> handler) {
		register(javaTypeReference.getRawType(), handler);
	}

	public UnknownTypeHandler getUnknownTypeHandler() {
		return UNKNOWN_TYPE_HANDLER;
	}
}
